<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/ol.min.css"
    integrity="sha512-bc9nJM5uKHN+wK7rtqMnzlGicwJBWR11SIDFJlYBe5fVOwjHGtXX8KMyYZ4sMgSL0CoUjo4GYgIBucOtqX/RUQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer" />
  <title>地图裁剪</title>

  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    html,
    body,
    #app,
    .app-map {
      height: 100%;
    }

    .app-btns {
      position: fixed;
      right: 10px;
      top: 10px;
      background-color: #fff;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
      width: 210px;
      padding: 25px;
      text-align: center;
      border-radius: 5px;
      display: flex;
      flex-direction: column;
    }

    .app-btns button {
      font-size: 18px;
      border: none;
      padding: 12px 20px;
      border-radius: 4px;
      color: #fff;
      background-color: #409eff;
      border-color: #409eff;
      cursor: pointer;
      border: 1px solid #dcdfe6;
      margin-bottom: 5px;
    }

    .app-btns button.active,
    .app-btns button:hover {
      background-color: rgba(7, 193, 96, 0.8);
    }
  </style>

</head>

<body>
  <div id="app">
    <div class="app-map" id="map"></div>
    <div class="app-btns">
      <button type="button" @click='handleClickInitMap(1)' :class='{active: type === 1}'>自己请求JOSN初始化地图</button>
      <button type="button" @click='handleClickInitMap(2)' :class='{active: type === 2}'>ol请求JOSN初始化地图</button>
      <button type="button" @click='handleClickGoToChongQing' v-show='map'>测试跳转到重庆</button>
    </div>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.14/vue.global.prod.min.js"
    integrity="sha512-huEQFMCpBzGkSDSPVAeQFMfvWuQJWs09DslYxQ1xHeaCGQlBiky9KKZuXX7zfb0ytmgvfpTIKKAmlCZT94TAlQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/dist/ol.min.js"
    integrity="sha512-+nvfloZUX7awRy1yslYBsicmHKh/qFW5w79+AiGiNcbewg0nBy7AS4G3+aK/Rm+eGPOKlO3tLuVphMxFXeKeOQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>

  <script>
    console.log(`openlayers版本号： ${ol.util.VERSION}`);
    const { createApp } = Vue;
    // 隐藏缩放控件
    const controls = ol.control.defaults.defaults({
      zoom: false
    });
    // 瓦片图层
    const mainLayer = new ol.layer.Tile({
      source: new ol.source.XYZ({
        url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
      })
    });
    // 裁剪需要的样式
    const style = new ol.style.Style({
      fill: new ol.style.Fill({
        color: 'black' // 设置为多少无所谓，设置了就行
      })
    });
    const vm = createApp({
      data() {
        return {
          map: null, // 地图实例
          type: 0, // 加载JSON的方式
          hljBorderLayer: null // 边界图层
        }
      },
      methods: {
        // 点击加载GeoJSON并初始化地图
        handleClickInitMap(type) {
          if (this.map) {
            this.map.setTarget(null);
            this.map = null;
          }
          this.type = type;
          if (type === 1) {
            this.getJSON1();
          } else {
            this.getJSON2();
          }
        },
        // fetch请求json, 初始化地图
        async getJSON1() {
          // 请求边界
          const cqBorderGeoJSONRes = await fetch(`./230000.json`);
          const cqBorderGeoJSON = await cqBorderGeoJSONRes.json();
          // 创建边界图层
          this.hljBorderLayer = new ol.layer.Vector({
            style: new ol.style.Style({
              stroke: new ol.style.Stroke({
                color: '#07c160',
                width: 3
              })
            }),
            source: new ol.source.Vector({
              features: new ol.format.GeoJSON().readFeatures(cqBorderGeoJSON, { featureProjection: 'EPSG:3857' }),
            })
          });
          this.initMap();
          // 限制GeoJSON的区域不能完全移出屏幕，如移出，自动跳回
          this.map.on('moveend', () => {
            const [mapMinX, mapMinY, mapMaxX, mapMaxY] = this.map.getView().calculateExtent(this.map.getSize());
            // 检查 GeoJSON 的四个方向是否都在视图范围之外
            const [jsonMinX, jsonMinY, jsonMaxX, jsonMaxY] = this.hljBorderLayer.getSource().getExtent();
            if (mapMinX >= jsonMaxX) {
              console.log('左侧超出了');
            };
            if (mapMaxX <= jsonMinX) {
              console.log('右侧超出了');
            };
            if (mapMaxY <= jsonMinY) {
              console.log('上侧超出了');
            };
            if (mapMinY >= jsonMaxY) {
              console.log('下侧超出了');
            };
            const isOutMap = mapMinX >= jsonMaxX || mapMaxX <= jsonMinX || mapMaxY <= jsonMinY || mapMinY >= jsonMaxY;
            if (isOutMap) {
              this.map.getView().animate({
                center: ol.proj.fromLonLat([127.1715261, 48.4281034]),
                zoom: 6,
                duration: 0
              })
            }
          });
        },
        // ol去请求josn，初始化地图
        getJSON2() {
          this.hljBorderLayer = new ol.layer.Vector({
            style: new ol.style.Style({
              stroke: new ol.style.Stroke({
                color: '#07c160',
                width: 3
              })
            }),
            source: new ol.source.Vector({
              url: './230000.json',
              format: new ol.format.GeoJSON(),
            }),
          });
          this.initMap();
        },
        // 初始化地图
        initMap() {
          // Giving the clipped layer an extent is necessary to avoid rendering when the feature is outside the viewport
          // 官方的注释，貌似是为了避免范围外的地图渲染
          this.hljBorderLayer.getSource().on('addfeature', () => {
            mainLayer.setExtent(this.hljBorderLayer.getSource().getExtent());
          });
          // 地图渲染完成后触发
          mainLayer.on('postrender', (e) => {
            const vectorContext = ol.render.getVectorContext(e);
            e.context.globalCompositeOperation = 'destination-in';
            this.hljBorderLayer.getSource().forEachFeature(function (feature) {
              vectorContext.drawFeature(feature, style);
            });
            e.context.globalCompositeOperation = 'source-over';
          });
          // 初始化地图
          this.map = new ol.Map({
            layers: [mainLayer, this.hljBorderLayer],
            target: 'map',
            view: new ol.View({
              center: ol.proj.fromLonLat([127.1715261, 48.4281034]),
              zoom: 6
            }),
            controls
          });
        },
        // 跳转到重庆
        handleClickGoToChongQing() {
          this.map.getView().animate({
            center: ol.proj.fromLonLat([107.768711, 30.096964]),
            zoom: 6
          })
        }
      },
      mounted() {
        this.handleClickInitMap(1);
      }
    }).mount('#app');
  </script>
  
</body>

</html>